﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using System.Reflection;
using System.ComponentModel;
using Microsoft.Crm.Sdk.Query;

namespace Streamsol.Crm.XrmDevelopmentToolkit
{
    public static class XrmHelpers
    {
        /// <summary>
        /// Queries CRM for a record set.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="service"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static IEnumerable<TEntity> RetrieveMultiple<TEntity>(this CrmService service, QueryBase query)
            where TEntity : IXrmEntity, new()
        {
            TEntity xrmEntity = new TEntity();
            Dictionary<string, PropertyInfo> attributes = xrmEntity.GetXrmMappedFields();

            if (query.ColumnSet == null || query.ColumnSet.GetType() == typeof(AllColumns))
                query.ColumnSet = new XrmMappedColumnSet(attributes.Select(s => s.Key).ToArray());

            RetrieveMultipleRequest req = new RetrieveMultipleRequest()
            {
                Query = query,
                ReturnDynamicEntities = true
            };

            var resultSet = (RetrieveMultipleResponse)service.Execute(req);

            foreach (DynamicEntity entity in resultSet.BusinessEntityCollection.BusinessEntities)
            {
                yield return entity.AsXrmEntity<TEntity>(attributes);
            }

        }        
  
        /// <summary>
        /// Save will perform either a create or an update.
        /// </summary>
        /// <param name="service"></param>
        /// <param name="entity">The entity to save</param>
        /// <returns>The Id of the entity</returns>
        public static Guid Save(this CrmService service, IXrmEntity entity)
        {
            Guid id = entity.Id;

            if (entity.Id == Guid.Empty)
            {
                id = service.Create(entity);
            }
            else
            {
                service.Update(entity);
            }

            return id;
        }

        /// <summary>
        /// Creates the IXrmEntity in CRM as it's respective CRM entity.
        /// </summary>
        /// <param name="service"></param>
        /// <param name="entity">The entiy to be saved</param>
        /// <returns>The GUID of the newly created entity.</returns>
        public static Guid Create(this CrmService service, IXrmEntity entity)
        {
            //Convert IXrmEntity to a DynamicEntity
            DynamicEntity de = entity.AsDynamicEntity(CrmAction.Create);

            Guid entityId;

            //Invoke any listeners            
            entity.OnSaving(de);

            //Create
            entityId = service.Create(de);

            entity.OnSaved(entityId);

            return entityId;
        }

        /// <summary>
        /// Performs an update on the entity
        /// </summary>
        /// <param name="service"></param>
        /// <param name="entity"></param>
        public static void Update(this CrmService service, IXrmEntity entity)
        {
            DynamicEntity de = entity.AsDynamicEntity(CrmAction.Update);

            entity.OnSaving(de);

            service.Update(de);

            entity.OnSaved(entity.Id);
        }

        /// <summary>
        /// Deletes the entity from CRM.
        /// </summary>
        /// <param name="service"></param>
        /// <param name="entity"></param>
        public static void Delete(this CrmService service, IXrmEntity entity)
        {
            service.Delete(entity.GetCrmEntityName(), entity.Id);
        }

        /// <summary>
        /// Converts a IXrmEntity to a DynamicEntity
        /// </summary>
        /// <param name="entity"></param>
        /// <returns>A populated DynamicEntity</returns>
        public static DynamicEntity AsDynamicEntity(this IXrmEntity entity)
        {
            return AsDynamicEntity(entity, CrmAction.Undefinied);
        }

        /// <summary>
        /// Converts a IXrmEntity to a DynamicEntity
        /// </summary>
        /// <param name="entity"></param>
        /// <returns>A populated DynamicEntity</returns>
        internal static DynamicEntity AsDynamicEntity(this IXrmEntity entity, CrmAction crmAction)
        {
            XrmPropertyCollection propCollection = XrmPropertyCollection.GetXrmProperties(entity, crmAction);
            return ConstructDynamicEntity(entity.GetCrmEntityName(), propCollection);
        }
       
        /// <summary>
        /// Builds a DynamicEntity given the name and a propertyCollection.
        /// </summary>
        /// <param name="entityName"></param>
        /// <param name="propertyCollection"></param>
        /// <returns></returns>
        private static DynamicEntity ConstructDynamicEntity(string entityName, XrmPropertyCollection propertyCollection)
        {
            DynamicEntity de = new DynamicEntity(entityName);

            var propSet = from list in propertyCollection
                          select list.Property;

            de.Properties.AddRange(propSet.ToArray());

            return de;
        }

        /// <summary>
        /// Converts a DynamicEntity to a <typeparamref name="TEntity"/>
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="dynamicEntity"></param>
        /// <returns>A <typeparamref name="TEntity"/></returns>
        public static TEntity AsXrmEntity<TEntity>(this DynamicEntity dynamicEntity)
            where TEntity : IXrmEntity, new()
        {

            return dynamicEntity.AsXrmEntity<TEntity>(GetXrmMappedFields<TEntity>());
        }

        /// <summary>
        /// Converts a DynamicEntity to a <typeparamref name="TEntity"/>
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="dynamicEntity"></param>
        /// <param name="attributes"></param>
        /// <returns>A <typeparamref name="TEntity"/></returns>
        internal static TEntity AsXrmEntity<TEntity>(this DynamicEntity dynamicEntity, Dictionary<string, PropertyInfo> attributes)
           where TEntity : IXrmEntity, new()
        {
            return (TEntity)dynamicEntity.AsXrmEntity(new TEntity(), attributes);
        }

        /// <summary>
        /// Iterrates through each property of the Dictionary and constructs a IXrmEntity
        /// </summary>
        /// <param name="dynamicEntity"></param>
        /// <param name="entity"></param>
        /// <param name="attributes"></param>
        /// <returns></returns>
        internal static IXrmEntity AsXrmEntity(this DynamicEntity dynamicEntity, IXrmEntity entity, Dictionary<string, PropertyInfo> attributes)
        {
            foreach (var att in attributes)
            {
                object crmValue = dynamicEntity.GetCrmProperty(att.Key);

                if (crmValue != null)
                {
                    Func<object> GetCrmValue = XrmExtensions.ExtractValueFromCrmProperty(crmValue, att.Value.PropertyType);
                    object objValue = GetCrmValue();
                    //This line wil lthrow an ArgumentException if there is a type mismatch
                    att.Value.SetValue(entity, objValue, null);

                }
            }

            return entity;
        }

        /// <summary>
        /// Given an entity this will get all the XrmFieldMappingAttributes associated with it.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <returns>A dictionary containing the CRM attribute name as the key and the PorpertyInfo for the Value.</returns>
        internal static Dictionary<string, PropertyInfo> GetXrmMappedFields<TEntity>()
            where TEntity : IXrmEntity, new()
        {
            return GetXrmMappedFields(new TEntity());
        }

        /// <summary>
        /// Given an entity this will get all the XrmFieldMappingAttributes associated with it.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <returns>A dictionary containing the CRM attribute name as the key and the PorpertyInfo for the Value.</returns>
        internal static Dictionary<string, PropertyInfo> GetXrmMappedFields(this IXrmEntity entity)
        {
            Dictionary<string, PropertyInfo> attributes = new Dictionary<string, PropertyInfo>();

            PropertyInfo[] propertyInfo = entity.GetType().GetProperties();

            foreach (PropertyInfo pi in propertyInfo)
            {
                XrmFieldMappingAttribute[] customAttributes = (XrmFieldMappingAttribute[])pi.GetCustomAttributes(typeof(XrmFieldMappingAttribute), true);

                if (customAttributes != null && customAttributes.Length > 0)
                {
                    attributes.Add(customAttributes[0].AttributeName, pi);
                }
            }
            return attributes;
        }

        /// <summary>
        /// Find an entity by it's Guid.
        /// </summary>
        /// <param name="results"></param>
        /// <param name="guid"></param>
        /// <returns>An IXrmEntity</returns>
        public static IXrmEntity GetById(this IEnumerable<IXrmEntity> results, Guid entityId)
        {
            return results.Where(s => s.Id == entityId).SingleOrDefault();
        }

        /// <summary>
        /// Find an entity by it's Guid.
        /// </summary>
        /// <typeparam name="TEntity">A type of IXrmEntity</typeparam>
        /// <param name="results"></param>
        /// <param name="guid"></param>
        /// <returns>An <typeparamref name="TEntity"/> with a matching Id, or a default if none is found.</returns>
        public static TEntity GetById<TEntity>(this IEnumerable<TEntity> results, Guid entityId)
            where TEntity : IXrmEntity, new()
        {
            return results.Where(s => s.Id == entityId).SingleOrDefault();
        }

        internal static string GetPrimaryIdAttributeName(this BusinessEntity entity)
        {
            string entityName = entity.GetType().Name;

            return GetPrimaryIdAttributeName(entityName);
        }

        internal static string GetPrimaryIdAttributeName(this IXrmEntity entity)
        {
            string entityName = entity.GetCrmEntityName();

            return GetPrimaryIdAttributeName(entityName);
        }

        internal static string GetPrimaryIdAttributeName(string entityName)
        {
            string[] activityFields = new string[] { "email", "task", "phonecall", "fax", "letter", "appointment", "serviceactivity", "campaignresponse" };

            return activityFields.Contains(entityName) ? "activityid" : entityName + "id";
        }

        public static Lookup CreateLookup(this IXrmEntity entity)
        {
            return new Lookup(entity.GetCrmEntityName(), entity.Id);
        }
    }
}
